package com.ra.util;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.jdbc.ReturningWork;
import org.springframework.stereotype.Service;

import com.espirit.eap.pagelayout.ModelService;
import com.espirit.eap.pagelayout.SearchModelQuery;
import com.googlecode.cswish.Config;
import com.googlecode.cswish.annotation.Rest;
import com.googlecode.cswish.exception.ApplicationException;
import com.googlecode.cswish.model.PageInfo;
import com.googlecode.cswish.model.PageItemMatcher;
import com.googlecode.cswish.model.Reference;
import com.googlecode.cswish.oosearch.QLInfo;
import com.googlecode.cswish.struts.Initialization;
import com.googlecode.cswish.struts.spring.Executor;
import com.googlecode.cswish.struts.spring.GenericService;
import com.googlecode.cswish.struts.spring.SystemEnv;
import com.googlecode.cswish.util.bean.ModelUtil;
import com.ra.util.baidu.BaiduAddress;
import com.ra.util.baidu.BaiduApiUtils;
import com.ra.util.baidu.BaiduPlaceDetail;

@Service
public class AddressCodeService implements Initialization {
	
	private final static Log logger = LogFactory.getLog(AddressCodeService.class);

	@Resource
	private GenericService genericService;
	
	@Resource
	private Config config;
	
	@Resource
	private ModelService modelService;
	
	@PostConstruct
	private void init() {
		modelService.registerSearchType(AddressCode.class.getName(), new Executor<PageInfo, SearchModelQuery>() {
			@Override
			public PageInfo<Map<String, String>> execute(SearchModelQuery searchModelQuery) {
				int pageNo = searchModelQuery.pageNo == null ? 1 : searchModelQuery.pageNo;
				int pageSize = searchModelQuery.pageSize == null ? 10 : searchModelQuery.pageSize;
				int start = pageNo * pageSize;
				int limit = pageSize;
				boolean onlyTop3Level = true;
				if ("*".equals(searchModelQuery.filter)) {
					onlyTop3Level = false;
				}
				AddressCode code = new AddressCode();
				code.setName(searchModelQuery.query);
				PageInfo pageInfo = search(code, onlyTop3Level, start, limit, null, null, false, null);
				return pageInfo;
			}
		});
	}
	
	@Override
	public void initSite() {
		boolean isEclipseEnv = SystemEnv.runningInEclipse();
		if (!isEclipseEnv) {
			AreaParser.getAddressCode("");		// force AreaParser to load the xml file
		}
	}
	
	@Override
	public String[] getProductName() {
		return RaConstant.PRODUCT_NAME;
	}
	
	
	@Rest(simpleServiceName = "anonymity", type = "free")
    public <T> PageInfo<T> search(T model, boolean onlyTop3Level, int start, int limit,
    		String sort, String dir, boolean oldSearch, Reference<QLInfo> qlInfoRef) {
    	if (onlyTop3Level) {
    		int pageSize = limit;
    		final String name = (String) ModelUtil.getProperty(model, "name");
    		PageInfo pageInfo = PageInfo.convertToPageInfo(AreaParser.getAddressCodes(), new PageItemMatcher<AddressCode, AddressCode>() {
				@Override
				public boolean matched(AddressCode code) {
					boolean pass = StringUtils.isEmpty(name) || code.getName().indexOf(name) != -1;
					if (!pass) {
						String smallName = name.toLowerCase();
						pass = code.getPinyinName().indexOf(smallName) != -1
							|| code.getPinyinShortName().indexOf(smallName) != -1;
					}
					return pass;
				}
				@Override
				public AddressCode toResult(AddressCode code) {
					return code;
				}
    		}, -1, pageSize);
    		return pageInfo;
    	} else {
    		return genericService.search(model, start, limit, sort, dir, oldSearch, qlInfoRef, null, true, false);
    	}
    }
	
	@Rest(simpleServiceName = "anonymity", type = "free")
	public PageInfo<AddressCode> searchCode(String name, Integer parentId, int minResultSize, int start, int limit) {
		QLInfo qlInfo = buildSearchCondition(name, parentId, start, limit);
		
		PageInfo<AddressCode> pageInfo = genericService.searchByQl(qlInfo);
		
		// 去除parentId的条件, 继续查找
		if (parentId != null && pageInfo.getData().size() < minResultSize && start == 0) {
			QLInfo qlInfoByName = buildSearchCondition(name, null, 0, limit - pageInfo.getData().size());
			PageInfo<AddressCode> pageInfoByName = genericService.searchByQl(qlInfo);
			pageInfo.getData().addAll(pageInfoByName.getData());
		}
		return pageInfo;
	}
	
	@Rest(simpleServiceName = "anonymity", type = "free")
	public List<AddressCode> getParentCodeList(Integer codeId) {
		AddressCode addressCode = genericService.load(AddressCode.class, codeId);
		List<AddressCode> codeList = new ArrayList<AddressCode>(4);
		if (addressCode != null) {
			int maxCount = 0;
			while (addressCode.getParent() != null && maxCount < 5) {
				addressCode = addressCode.getParent();
				maxCount++;
				codeList.add(addressCode);
			}
			
			if (maxCount >= 5) {
				throw new ApplicationException("太多层级", codeId);
			}
		}
		return codeList;
	}

	private QLInfo buildSearchCondition(String name, Integer parentId, int start, int limit) {
		List<Object> parameters = new ArrayList<>();
		StringBuilder where = new StringBuilder();
		if (name != null && name.length() > 0) {
			char firtChar = name.charAt(0);
			boolean isPinyin = firtChar >= 'a' && firtChar <= 'z' || firtChar >= 'A' && firtChar <= 'Z';
			if (isPinyin) {
				where.append("a.pinyinName like ? or a.pinyinShortName like ?");
				parameters.add(name + '%');
				parameters.add(name + '%');
			} else {
				where.append("a.name like ?");
				parameters.add(name + '%');
			}
		}
		
		if (parentId != null) {
			if (where.length() > 0) {
				where.append(" and ");
			}
			
			if (parentId == 0) {
				where.append("a.parent.id is null");
			} else {
				where.append("a.parent.id=?");
				parameters.add(parentId);
			}
		}
		
		String ql;
		if (where.length() <= 0) {
			ql = "from " + AddressCode.class.getName() + " a";
		} else {
			ql = "from " + AddressCode.class.getName() + " a where " + where;
		}
		
		QLInfo qlInfo = new QLInfo(ql, true, parameters);
		if (start < 0) {
			start = 0;
		}
		qlInfo.setFirstResult(start);
		if (limit <= 0 || limit > 1000) {
			limit = 1000;
		}
		qlInfo.setMaxResults(limit);
		return qlInfo;
	}
	
	@Rest(simpleServiceName = "anonymity", type = "free")
	public void fixAddressCodeInfo() {
		genericService.doReturningWork(new ReturningWork<Boolean>() {
			@Override
			public Boolean execute(Connection connection) throws SQLException {
				boolean ret = connection.getAutoCommit();
				connection.setAutoCommit(false);
				
				String sql = "update ra_addresscode set ra_addresscode.longitude = ?, ra_addresscode.latitude = ? where ra_addresscode.id = ?";
				PreparedStatement updatePs = connection.prepareStatement(sql);
				int record = 0;
				int firstResult = 0;
				try (
						PreparedStatement ps = connection.prepareStatement("select c.id from ra_addresscode c where c.longitude is null and c.latitude is null");
						ResultSet rs = ps.executeQuery()) {
					while (rs.next()) {
						Integer id = rs.getInt(1);
						AddressCode addressCode = genericService.load(AddressCode.class, id);
						Double lng = null;
						Double lat = null;
						String city = AreaParser.getAddressCode(AreaParser.buildAreaCode(getCodeByLevel(addressCode, AddressCode.LEVEL_PROVINCE),
								getCodeByLevel(addressCode, AddressCode.LEVEL_CITY), getCodeByLevel(addressCode, AddressCode.LEVEL_DISTRICT))).getFullName();
						BaiduAddress baiduAddress = BaiduApiUtils.findCoordinateByAddress(city, addressCode.getFullName());
						if (baiduAddress != null) {
							lat = baiduAddress.getLatitude();
							lng = baiduAddress.getLongitude();
						}
						
						int index = 0;
						setDouble(updatePs, ++ index, lng);
						setDouble(updatePs, ++ index, lat);
						setInt(updatePs, ++index, id);
						updatePs.addBatch();
						if (++record % 1000 == 0) {
							updatePs.executeBatch();
							logger.info("~~~~~~~~~~~process addresscode from " + firstResult + " to " + (firstResult + 1000));
							firstResult += 1000;
						}
					}
				}
				updatePs.executeBatch();
				updatePs.close();
				connection.commit();
		        connection.setAutoCommit(ret);
				return Boolean.TRUE;
			}
			
			private void setInt(PreparedStatement ps, int index, Integer value) throws SQLException {
				if (value != null) {
					ps.setInt(index, value);
				} else {
					ps.setNull(index, Types.INTEGER);
				}
			}
			
			private void setDouble(PreparedStatement ps, int index, Double value) throws SQLException {
				if (value != null) {
					ps.setDouble(index, value);
				} else {
					ps.setNull(index, Types.DOUBLE);
				}
			}
		});
	}
	
	@Rest(simpleServiceName = "anonymity", type = "free")
	public Address convertBaiduAddress(BaiduPlaceDetail baiduAddress) {
		Address address = new Address();
		
		if (StringUtils.isEmpty(baiduAddress.getProvince())) {
			Area provinceArea = AreaParser.findProvinceByCity(baiduAddress.getCity());
			if (provinceArea != null) {
				baiduAddress.setProvince(provinceArea.getAreaName());
			}
		}
		
		String[] codes = AreaParser.findAreaCode(baiduAddress.getProvince(), baiduAddress.getCity(), baiduAddress.getDistrict());
		if (codes[0] != null) {
			address.setProvince(new Integer(codes[0]));
		}
		if (codes[1] != null) {
			address.setCity(new Integer(codes[1]));
		}
		if (codes[2] != null) {
			address.setDistrict(new Integer(codes[2]));
		}
		address.setName(baiduAddress.getName());
		
		if (address.getLatitude() == null || address.getLongitude() == null) {
			address.setLongitude(baiduAddress.getLongitude());
			address.setLatitude(baiduAddress.getLatitude());
		}
		return address;
	}
	
	public Address convertBaiduAddress(BaiduAddress baiduAddress) {
		Address address = new Address();
		
		if (StringUtils.isEmpty(baiduAddress.getProvince())) {
			Area provinceArea = AreaParser.findProvinceByCity(baiduAddress.getCity());
			if (provinceArea != null) {
				baiduAddress.setProvince(provinceArea.getAreaName());
			}
		}
		
		String[] codes = AreaParser.findAreaCode(baiduAddress.getProvince(), baiduAddress.getCity(), baiduAddress.getDistrict());
		if (codes[0] != null) {
			address.setProvince(new Integer(codes[0]));
		}
		if (codes[1] != null) {
			address.setCity(new Integer(codes[1]));
		}
		if (codes[2] != null) {
			address.setDistrict(new Integer(codes[2]));
		}
		address.setName(baiduAddress.getAddress());
		
		if (address.getLatitude() == null || address.getLongitude() == null) {
			address.setLongitude(baiduAddress.getLongitude());
			address.setLatitude(baiduAddress.getLatitude());
		}
		return address;
	}
	
	public Integer getCodeByLevel(AddressCode addressCode, int level) {
		if (level > addressCode.getLevel()) {
			return null;
		} else if (level == addressCode.getLevel()) {
			return addressCode.getCode();
		} else {
			// find the parent code
			AddressCode parent = genericService.load(addressCode.getParent());
			while (parent != null) {
				if (parent.getLevel() == level) {
					return parent.getCode();
				}
				parent = genericService.load(parent.getParent());
			}
			return null;
		}
	}
}